import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;


/**
 * Tato třída je určena k demostraci toho jak vytvořit prázdný obrázek, jak měnit
 * jednotlivé pixely v obrázku a jak obrázek zobrazit na obrazovce
 * při změně nějakých parametrů (v tomto případě při změně pozice na posuvníku).
 * 
 * @author Ladislav Čmolík
 */
public class ImageDisplay extends JPanel implements ChangeListener{

    // Konstanty určující velikost obrázku
    private final int IMG_WIDTH = 255;
    private final int IMG_HEIGHT = 255;
    
    private final int X_AXIS = 10;
    private final int Y_AXIS = 20;
    private final int Z_AXIS = 30;
    private int currentAxis;
    
    private final int FINAL_IMG_WIDTH = 500;
    private final int FINAL_IMG_HEIGHT = 500;
    private final int NUM_OF_IMAGES = 113;
    private final int Z_SCALE = 2;
    // Konstranty určující barevné mapování
    private final int SPECTRUM = 1;
    private final int GRAY = 2;
    private final int BW = 3;
    // Proměnná určující barevné mapování
    private int mapping = GRAY;
    private Color[] colours;
    //maximalni hodnota sede v PNG16 obrazku
    int imgMaxGray = 3272;
    int numOfColours = 255;
    //maximalni hodnota v BufferedImage.TYPE_USHORT_GRAY
    int maxGray = 65534;
    
    int slider_value = 0;
    
   
    // Od Toma Altmana_______
    //Aktualni rez
    int actRez; 
    //Transformujici matrix
    Matrix transMatrix;
   //Normala roviny xy
    double[]normal = {0.,0.,1.};
    Matrix normalMatrix = new Matrix(normal);
    
    // Zadana libovolna normala
    double[]randNormal = {0., 1., 0.};
    Matrix randNormalMatrix = new Matrix(randNormal);

    // 113 rezu lebky
    BufferedImage[] images;
   //Aktualni obrazek
    BufferedImage image;
    // Rastr obrázku
    WritableRaster raster;
    

    /**
     * Vytvoří prázdný RGB obrázek kde na každý kanál jednoho pixelu připadá 8 bitů,
     * získá z obrázku rastr a nastaví rozměry JPanelu na velikost obrázku.
     */
    public ImageDisplay() {
        image = new BufferedImage(FINAL_IMG_WIDTH, FINAL_IMG_HEIGHT, 
                    BufferedImage.TYPE_INT_RGB);
        raster = image.getRaster();
        Dimension dim = new Dimension(FINAL_IMG_WIDTH, FINAL_IMG_HEIGHT);
        initGrayMapping();
        setMinimumSize(dim);
        setPreferredSize(dim);
        setMaximumSize(dim);
        currentAxis = Z_AXIS;
        initImages();
        interpolateImages();
    }
    
    public int getCurrentAxis(){
        return currentAxis;
    }
    
     /**
     * Na základě x,y,z souřadnic v krychli dat vrátí rozinterpolovanou hodnotu z osmi 
     * sousedních vrcholů
     * 
     * @param x .... x souradnice bodu v krychli - rozsah 0-500
     * @param y .... y souradnice bodu v krychli - rozsah 0-500
     * @param z .... z souradnice bodu v krychli - musi byt v rozsahu 0-133!
     * 
     */
    private int interpolatePoint(float x, float y, float z){
        int gray;
        int x0 = (int) Math.floor(x);
        int x1 = (int) Math.ceil(x);
        int y0 = (int) Math.floor(y);
        int y1 = (int) Math.ceil(y);
        int z0 = (int) Math.floor(z);
        int z1 = (int) Math.ceil(z);
        float a = x1 - x;
        float b = z1 - z;
        float c = y - y0;
        
        if(z1 >= 226)
            z1 = 225;
        if(z0 >= 226)
            z0 = 225;
        if(x0>=255)
            x0 = 254;
        if(x1>=255)
            x1 = 254;
        if(y0>=255)
            y0 = 254;
        if(y1>=255)
            y1 = 254;

        /*
         * Hodnoty osmi sousednich pixelu
         * Notace: FLR = Front lower right
         *         BUL = Back upper left
         */
        int FLL = images[z0].getRaster().getSample(x0,y0,0);
        int FLR = images[z0].getRaster().getSample(x1,y0,0);
        int FUR = images[z0].getRaster().getSample(x1,y1,0);
        int FUL = images[z0].getRaster().getSample(x0,y1,0);
        //back face
        int BLL = images[z1].getRaster().getSample(x0,y0,0);
        int BLR = images[z1].getRaster().getSample(x1,y0,0);
        int BUR = images[z1].getRaster().getSample(x1,y1,0);
        int BUL = images[z1].getRaster().getSample(x0,y1,0);
        gray = Math.round((1-a)*((1-b)*((1-c)*FLR+c*FUR)+b*((1-c)*BLR+c*BUR))+a*((1-b)*((1-c)*FLL+c*FUL)+b*((1-c)*BLL+c*BUL)));
        return gray;
    }
    
    
    private float[] transform2OpenglCoords(float x, float y, float z){
        float[] res = new float[3];
        
        float widthRatio = (float)IMG_WIDTH/(float)FINAL_IMG_WIDTH;
        float heightRatio = (float)IMG_HEIGHT/(float)FINAL_IMG_HEIGHT;
        
        res[0] = widthRatio*x;
        res[1] = -y + (float)IMG_HEIGHT;
        res[2] = -z + (float)NUM_OF_IMAGES * (float)Z_SCALE;
        return res; 
    }
    
    /**
     * Na zaklade obrazovych bodu u, j, hodnoty posuvniku value a osy
     * vrati voxel, ve kterem se bod nachazi
     * 
     * @param u .... x souradnice bodu v krychli - rozsah 0-500
     * @param j .... y souradnice bodu v krychli - rozsah 0-500
     * @param value .... z souradnice bodu v krychli - musi byt v rozsahu 0-133!
     * @param axis .... z souradnice bodu v krychli - musi byt v rozsahu 0-133!
     * 
     */
    private Voxel getVoxel(float u, float j, float value, int axis){
        Voxel ret = new Voxel();
       
       switch (axis){
            case Z_AXIS:
                ret.x = ((float)IMG_WIDTH/(float)FINAL_IMG_WIDTH)*u;
                ret.y = ((float)IMG_HEIGHT/(float)FINAL_IMG_HEIGHT)*j;
                ret.z = (float)value;
                break;
            case Y_AXIS:
                ret.x = IMG_WIDTH - ((float)IMG_WIDTH/(float)FINAL_IMG_WIDTH)*u;
                ret.y = value;
                ret.z = (((float)Z_SCALE*(float)NUM_OF_IMAGES)/(float)FINAL_IMG_WIDTH)*j;
                break;
            case X_AXIS:
                ret.x = IMG_WIDTH - value;
                ret.y = ((float)IMG_HEIGHT/(float)FINAL_IMG_HEIGHT)*j;
                ret.z = (((float)Z_SCALE*(float)NUM_OF_IMAGES)/(float)FINAL_IMG_WIDTH)*u;
                break;
        }
        return ret;
    }
    
    private void initImages(){
        ImageLoader imageLoader = new ImageLoader();
        images = new BufferedImage[2*NUM_OF_IMAGES];
        
        String number;
        for(int i = 0; i<NUM_OF_IMAGES; i++){
            if(i<9){ 
                number = "00"+ Integer.toString(i+1);
            } else if(i < 99){
                number = "0"+ Integer.toString(i+1);
            } else{
                number = Integer.toString(i+1);
            }
            
            images[2*i] = imageLoader.loadImage("./cthead/cthead-16bit"  + number + ".png");  
        }
    }
    
    private void interpolateImages(){
        WritableRaster rastPrev;
        WritableRaster rastForw;
        for(int i = 1; i<NUM_OF_IMAGES*2-1; i+=2){
             images[i] = new BufferedImage(IMG_WIDTH, IMG_HEIGHT, 
                    BufferedImage.TYPE_USHORT_GRAY);
            rastPrev = images[i-1].getRaster();
            rastForw = images[i+1].getRaster();
            for(int x = 0; x<IMG_WIDTH; x++){
                for(int y = 0; y<IMG_HEIGHT; y++){
                    images[i].getRaster().setSample(x, y, 0, (rastPrev.getSample(x, y, 0) + rastForw.getSample(x, y, 0))/2. );
                }
            }
        }
        // posledni obrazek je stejny jak predposledni
        images[225] = images[224];
    }
    
    public void setImagePixel(int x, int y, Color c){
            int[] colour = {c.getRed(), c.getGreen(), c.getBlue()};
            raster.setPixel(x, y, colour);
    }
    
    public void setImagePixel(int x, int y, int gray){
            raster.setSample(x, y, 0, gray);
    }
    
     /*
     * Prevede hodnoty od 0 do 3272 na cernobily obrazek
     * 
     * @return - int[] ... index 0-R, 1-G, 2-B
     */
    public Color PNG16ToColour(int gray){
        int[] ret = new int[3];
        gray = (int) gray*numOfColours/imgMaxGray;
        Color c = colours[gray];
        return c;
    }
    
     /*
     * Prevede hodnoty od 0 do 3272 na cervenozlute mapovani
     * 
     * @return - int[] ... index 0-R, 1-G, 2-B
     */
    public int[] PNG16ToRB(int gray){
        int[] ret = new int[3];
        float grayScale = (float)gray/(float)imgMaxGray;
        if(gray <= 800){
            ret[0] = (int) (128*(grayScale));
            ret[1] = 0;
            ret[2] = 0;
        }
        else if(gray > 800 && gray <= 1600){
            ret[0] = (int) (128+128*(grayScale));
            ret[1] = (int) (128*(grayScale));
            ret[2] = 0;
        }   
        else if(gray > 1600 && gray <= 2400){
            ret[0] = 255;
            ret[1] = (int) (128+128*(grayScale));
            ret[2] = (int) (128*(grayScale));
        }    
        else{
            ret[0] = 255;
            ret[1] = 255;
            ret[2] = 255;
        }
        return ret;
    }
    
     /**
     * Zapíše na danou pozici pixelu [x,y] barvu slozenou z RGB.
     */
    
    public void setImagePixel(int x, int y, int r, int g, int b){
        raster.setSample(x,y,0,r);
        raster.setSample(x,y,1,g);
        raster.setSample(x,y,2,b);
    }
    
    /*
     * 
     * Vykresli obrazek, ktery se nachazi na urcitem indexu pole obrazku
     * 
     */
    public void generateImage(int index){
        Color colour;
        Voxel v;
        float z = index;
        if(currentAxis == Z_AXIS)
            z = (((float)225/(float)255)*(float)index);
        for(int x = 0; x <FINAL_IMG_WIDTH; x++){
            for(int y = 0; y <FINAL_IMG_HEIGHT; y++){
                v = getVoxel(x, -y + (float)FINAL_IMG_HEIGHT, -z + (float)NUM_OF_IMAGES * (float)Z_SCALE , currentAxis);
                int picture_value = interpolatePoint(v.x, v.y, v.z);
                switch(mapping){
                    case SPECTRUM:
                        colour = PNG16ToColour(picture_value);
                        break;
                    case GRAY:
                        colour = PNG16ToColour(picture_value);
                        break;
                    case BW:
                        colour = PNG16ToColour(picture_value);
                        break;
                    default:
                        colour = PNG16ToColour(picture_value);
                        break;
                }
                setImagePixel(x, y, colour);
            }
        }
     }
    
    /**
     * Zapíše do každého pi.xelu předané hodnoty.
     * Hodnoty musí být v rozsahu 0 až 255. Pixel 
     * (0,0,0) je černý a pixel (255, 255, 255) je bílý.
     */
    public void generateImage(int r, int g, int b) {
        for(int x = 0; x < IMG_WIDTH; x++) {
            for(int y = 0; y < IMG_HEIGHT; y++) {
                raster.setSample(x, y, 0, r);
                raster.setSample(x, y, 1, g);
                raster.setSample(x, y, 2, b);
            }
        }
    }
    
    public BufferedImage getCurrentImage(){
        return image;
    }
     
    private void initGrayMapping(){
       colours = new Color[numOfColours+1];
       for (int i = 0; i < numOfColours+1; i++) {
            colours[i] = new Color((255)/numOfColours*i, (255)/numOfColours*i, (255)/numOfColours*i);
       }
    }
    
    private void initSpectrumMapping(){
        colours = new Color[numOfColours+1];
        for (int i = 0; i < numOfColours+1; i++) {
            colours[i] = Color.getHSBColor((float)i/(float)numOfColours, 1.0f, 1.0f);
        }        
    }
    
    private void initBWMapping(){
        colours = new Color[numOfColours+1];
        for (int i = 0; i < numOfColours+1; i++) {
            colours[i] = Color.getHSBColor(0.0f, 0.0f, 1 - (float)i/(float)numOfColours);
        }        
    }
    
    public void setMapping(String newMapping){
        if(newMapping.equals("Stupně šedi")){
            initGrayMapping();
            mapping = GRAY;
        }
        if(newMapping.equals("Barevné spektrum")){
            initSpectrumMapping();
            mapping = SPECTRUM;
        }
        if(newMapping.equals("Černobílé")){
            initBWMapping();
            mapping = BW;
        }
            
    }
        
    public void setAxis(String newMapping){
        if(newMapping.equals("Osa x"))
            currentAxis = X_AXIS;
        if(newMapping.equals("Osa y"))
            currentAxis = Y_AXIS;
        if(newMapping.equals("Osa z"))
            currentAxis = Z_AXIS;
    }

    /**
     * Vykreslí obrázek do panelu. 
     */
    @Override
    public void paint(Graphics g) {
        Graphics2D g2 = (Graphics2D) g;
        g2.drawImage(image, 0, 0, this);
    }

    /**
     * Implementace rozhraní ChangeListener volá se vždy když se změní hodnota
     * na posuvníku. Na základě hodnoty na posuvníku je překreslen obsah obrázku
     * a obrázek je zobrazen.
     */
    @Override
    public void stateChanged(ChangeEvent e) {
        JSlider slider = (JSlider) e.getSource();
        int value = slider.getValue();
        generateImage(value);
        // Metoda repaint() způsobí, že se zavolá metoda paint() této třídy.
        repaint();
    }
   
}
